﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading.Tasks.Dataflow;
using ARSoft.Tools.Net;
using ARSoft.Tools.Net.Dns;
using LIK.NetworkTools;

namespace LIK.WakeOnLan.Network
{

    public class NetworkClients : List<NetworkClient>
    {
        public static NetworkClients Instance = null;

        public static bool FirstRun = true;

        static NetworkClients()
        {
            if (Instance == null)
            {
                Instance = ApplicationConfigSaver.LoadNetworkClients();
            }
        }

        internal System.Collections.IEnumerable ClearAllExcludinFavorites()
        {
            for(int i= this.Count-1;i> -1; i--)
            {
                if (this[i].FavStatus == NetworkClientFavoritesStatus.None)
                {
                    this.RemoveAt(i);
                }
            }
            return this;
        }

        internal System.Collections.IEnumerable ClearAll()
        {
            this.Clear();
            return this;
        }

        internal static void ResolveNames_()
        {
            System.Threading.Tasks.Parallel.ForEach(Instance, networkClient =>
            {
                if (networkClient.IsUnresolvedHostName() || networkClient.HostName.ToString().Contains(".in-addr.arpa."))
                {
                    DomainName name = IPAddressExtensions.GetReverseLookupDomain(networkClient.GetIpAddress());

                    if (name == null || name.ToString().Contains(".in-addr.arpa.") )
                    {
                        networkClient.HostName = GetReverseDns(networkClient.IpAddress, 5000);
                    }
                    else
                    {
                        networkClient.HostName = name.ToString();
                    }
                }
            });
        }

        private delegate IPHostEntry GetHostEntryHandler(string ip);

        public static string GetReverseDns(string strIpAddress, int timeout)
        {
            try
            {
                //var host1 = Dns.GetHostByAddress(nClient.IpAddress);
                GetHostEntryHandler callback = new GetHostEntryHandler(Dns.GetHostEntry);
                IAsyncResult result = callback.BeginInvoke(strIpAddress, null, null);
                if (result.AsyncWaitHandle.WaitOne(timeout, false))
                {
                    return callback.EndInvoke(result).HostName;
                }
                else
                {
                    IDnsResolver resolver = new DnsStubResolver();
                    DomainName name = resolver.ResolvePtr(IPAddress.Parse(strIpAddress));

                    return name == null ? "?" : name.ToString();
                }
            }
            catch (Exception)
            {
                return "?";
            }
        }

        internal static void ResolveNames()
        {
            var block = new ActionBlock<NetworkClient>(
                async networkClient =>
            {
                try
                {
                    //IPHostEntry entry = Dns.GetHostEntry(pAddress);
                    if (networkClient.IsUnresolvedHostName())
                    {
                        var host = (await Dns.GetHostEntryAsync(networkClient.IpAddress)).HostName;
                        networkClient.HostName = !string.IsNullOrWhiteSpace(host) ? host : "?";
                    }
                }
                catch
                {
                    networkClient.HostName = "?";
                }
            },
            new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5000 });

            foreach (var networkClient in Instance)
            {
                if (networkClient.IsUnresolvedHostName())
                {
                    block.Post(networkClient);
                }
            };

            // Set the block to the completed state and wait for all tasks to finish.
            block.Complete();
            block.Completion.Wait();
        }

        public static void LocalNetworkScan()
        {
            FillFromIptable();
            CompleteFromFullNetworkScan(NicEnumerator.Instance.SelectedNic.Ip);
        }

        public static void NetworkScan(string strNetworkIpAddress)
        {
            CompleteFromFullNetworkScan(strNetworkIpAddress);
        }

        private static void ResetOnlineStatus()
        {
            foreach (var nClient in Instance)
            {
                nClient.Status = NetworkClientStatus.Down;
            }
        }

        private static void FillFromIptable()
        {
            ResetOnlineStatus();
            foreach (KeyValuePair<IPAddress, PhysicalAddress> kvp in NetworkIpTable.GetAllDevicesOnLan())
            {
                string strIpAddress = kvp.Key.ToString();

                var networkClient = Instance.Find(nc => nc.IpAddress.Equals(strIpAddress));
                if (networkClient == null)
                {
                    var mac = IpHelper.NotFound;
                    if (kvp.Value != null)
                    {
                        mac = string.Join(":", (from z in kvp.Value.GetAddressBytes() select z.ToString("X2")).ToArray());
                    }
                    else
                    {
                        if (kvp.Key.GetIpMacAddress(out mac))
                        {
                            mac = IpHelper.NotFound;
                        }
                    }
                    string hostName = string.Empty;

                    NetworkClient nClient = new NetworkClient(hostName, strIpAddress, mac)
                    {
                        IpMask = NicEnumerator.Instance.SelectedNic.Mask
                    };
                    Instance.Add(nClient);
                }
            }
        }

        public static void PingSingle(string strClientIpAddress)
        {
            var networkClient = Instance.Find(nc => nc.IpAddress.Equals(strClientIpAddress));
            if (networkClient != null)
            {
                networkClient.Status = NetworkScanner.IsOnline(strClientIpAddress) ? NetworkClientStatus.On : NetworkClientStatus.Down;
            }
        }

        private static void CompleteFromFullNetworkScan(string strNetworkIpAddress)
        {
            ResetOnlineStatus();
            //var localIpAddressBytes = IpHelper.GetLocalIpAddress().GetAddressBytes();
            var localIpAddressBytes = IPAddress.Parse(strNetworkIpAddress).GetAddressBytes();
            foreach (var kvp in
                NetworkScanner.ScanNetwork(localIpAddressBytes[0] + "." + localIpAddressBytes[1] + "." +
                                    localIpAddressBytes[2] + ".", localIpAddressBytes[3]))
            {
                string strIpAddress = kvp.Key.ToString();
                var networkClient = Instance.Find(nc => nc.IpAddress.Equals(strIpAddress));
                if (networkClient == null)
                {
                    string mac = string.Empty;
                    if (IpHelper.GetIpMacAddress(kvp.Key, out mac))
                    {
                    }
                    string hostName = string.Empty;
                    NetworkClient nClient = new NetworkClient(hostName, strIpAddress, mac)
                    {
                        Status = kvp.Value == IPStatus.Success.ToString() ? NetworkClientStatus.On : NetworkClientStatus.Down,
                        IpMask = NicEnumerator.Instance.SelectedNic.Mask
                    };
                    Instance.Add(nClient);
                }
                else
                {
                    networkClient.Status = kvp.Value == IPStatus.Success.ToString() ? NetworkClientStatus.On : NetworkClientStatus.Down;
                }

            }
        }


    }


}
